﻿using System;
using System.IO;
using System.Linq;
using devtm.Aop.Convert;
using Mono.Cecil;
using Mono.Collections.Generic;
using devtm.Aop.Extensions;
using System.Reflection;


namespace devtm.Aop
{


    public class CodeDomIILVisitorConverter
    {

        string pdbfile;
        string filename;
        string WorkDir;
        ModuleDefinition _module;
        MethodValidator validator;
        //private TypeDefinition _Interceptor;
        Converter converter;
        #region Ctor



        public CodeDomIILVisitorConverter(string filename, ILogging log, bool withpdb)
            : this(filename, GetPdbFilename(filename), "", log, withpdb)
        {



        }


        public CodeDomIILVisitorConverter(string filename, string pdbFilename, ILogging log, bool withpdb)
            : this(filename, pdbFilename, "", log, withpdb)
        {



        }


        public CodeDomIILVisitorConverter(string filename, string pdbFilename, string WorkDir, ILogging log, bool withpdb)
        {

            this.withPdb = withpdb;
            this._log = log;

            this.filename = filename;

            if (File.Exists(pdbFilename))
                pdbfile = pdbFilename;

            this.WorkDir = WorkDir;

            Initialize();

        }

        private static string GetPdbFilename(string filename)
        {
            return Path.ChangeExtension(filename, ".pdb");
        }

        public void Initialize()
        {
            AssemblyDefinition c = filename.LoadAssemblyMono();
            _module = c.MainModule;
            converter = new Converter(_module);
        }

        #endregion


        public void Analyze(MethodValidator validator, string outfile)
        {

            this.validator = validator;

            validator.Check(_module.Assembly);

            var lst = _module.Types.ToList();

            foreach (var t in lst)
                if (t != null)
                {

                    TypeDefinition type = t as TypeDefinition;

                    if (type.Name != "<Module>")
                        TypeDefinitionVisit(type);
                }
            
            validator.UnCheck(_module.Assembly);

            _module.Assembly.Write(outfile, new WriterParameters() { WriteSymbols = true });

        }


        public Assembly AnalyzeAndLoadMemory(MethodValidator validator)
        {

            this.validator = validator;

            validator.Check(_module.Assembly);

            var lst = _module.Types.ToList();

            foreach (var t in lst)
                if (t != null)
                {

                    TypeDefinition type = t as TypeDefinition;

                    if (type.Name != "<Module>")
                        TypeDefinitionVisit(type);
                }

            validator.UnCheck(_module.Assembly);

            Assembly ass = _module.Assembly.LoadAssemblyInMemory(AppDomain.CurrentDomain, withPdb);

            return ass;

        }


        #region Loops

        void TypeDefinitionVisit(TypeDefinition source)
        {

            validator.Check(source);

            if (source.HasMethods)
                MethodsVisit(source.Methods);

            if (source.HasEvents)
                EventsVisit(source.Events);

            if (source.HasProperties)
                PropertiesVisit(source.Properties);

            if (source.HasNestedTypes)
                NestedTypesVisit(source.NestedTypes);

            validator.UnCheck(source);

        }

        void MethodsVisit(Collection<MethodDefinition> source)
        {

            var lst = source.ToList();

            foreach (MethodDefinition item in lst)
                this.MethodVisit(item);

        }

        void PropertiesVisit(Mono.Collections.Generic.Collection<PropertyDefinition> source)
        {

            foreach (PropertyDefinition item in source)
                PropertyDefinitionVisit(item);

        }

        void EventsVisit(Mono.Collections.Generic.Collection<EventDefinition> source)
        {

            foreach (EventDefinition item in source)
                EventVisit(item);

        }

        void PropertyDefinitionVisit(PropertyDefinition item)
        {

            validator.Check(item);

            MethodVisit(item.GetMethod);
            MethodVisit(item.SetMethod);

            if (item.HasOtherMethods)
                MethodsVisit(item.OtherMethods);

            validator.UnCheck(item);

        }

        void EventVisit(EventDefinition source)
        {

            validator.Check(source);

            MethodVisit(source.InvokeMethod);

            MethodVisit(source.AddMethod);

            MethodVisit(source.RemoveMethod);

            if (source.HasOtherMethods)
                MethodsVisit(source.OtherMethods);

            validator.UnCheck(source);

        }

        void NestedTypesVisit(Mono.Collections.Generic.Collection<TypeDefinition> source)
        {

            foreach (TypeDefinition item in source)
                TypeDefinitionVisit(item);

        }

        #endregion


        void MethodVisit(MethodDefinition method)
        {

            if (method == null || validator.Validate(method))
                return;

            validator.Check(method);

            InterceptorContainer box = validator.GetBoxBehevior(method);

            if (!box.ContainsElement)
                return;            

            LogMessage("Treatment " + method.FullName);

            converter.ReplaceMethod(method, box);

            validator.UnCheck(method);
            validator.MethodUpdated(method);

        }

    
        #region Logs

        private ILogging _log;

        public void LogError(string message, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogError(message, messageArgs);
        }

        public void LogErrorFromException(Exception exception)
        {
            if (this._log != null)
                this._log.LogErrorFromException(exception);
        }

        public void LogErrorFromException(Exception exception, bool showStackTrace)
        {
            if (this._log != null)
                this._log.LogErrorFromException(exception, showStackTrace);
        }

        public void LogErrorFromException(Exception exception, bool showStackTrace, bool showDetail, string file)
        {
            if (this._log != null)
                this._log.LogErrorFromException(exception, showStackTrace, showDetail, file);
        }

        public void LogErrorFromResources(string messageResourceName, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogErrorFromResources(messageResourceName, messageArgs);
        }

        public void LogErrorFromResources(string subcategoryResourceName, string errorCode, string helpKeyword, string file, int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber, string messageResourceName, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogErrorFromResources(subcategoryResourceName, errorCode, helpKeyword, file, lineNumber, columnNumber, endLineNumber, endColumnNumber, messageResourceName, messageArgs);
        }

        public void LogErrorWithCodeFromResources(string messageResourceName, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogErrorWithCodeFromResources(messageResourceName, messageArgs);
        }

        public void LogErrorWithCodeFromResources(string subcategoryResourceName, string file, int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber, string messageResourceName, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogErrorWithCodeFromResources(subcategoryResourceName, file, lineNumber, columnNumber, endLineNumber, endColumnNumber, messageResourceName, messageArgs);
        }

        public void LogExternalProjectFinished(string message, string helpKeyword, string projectFile, bool succeeded)
        {
            if (this._log != null)
                this._log.LogExternalProjectFinished(message, helpKeyword, projectFile, succeeded);
        }

        public void LogExternalProjectStarted(string message, string helpKeyword, string projectFile, string targetNames)
        {
            if (this._log != null)
                this._log.LogExternalProjectStarted(message, helpKeyword, projectFile, targetNames);
        }

        public void LogMessage(string message, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogMessage(message, messageArgs);
        }

        public void LogMessageFromResources(string messageResourceName, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogMessageFromResources(messageResourceName, messageArgs);
        }

        public bool LogMessagesFromFile(string fileName)
        {
            if (this._log != null)
                return this._log.LogMessagesFromFile(fileName);

            return false;

        }

        public void LogWarning(string message, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogWarning(message, messageArgs);
        }

        public void LogWarning(string subcategory, string warningCode, string helpKeyword, string file, int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber, string message, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogWarning(subcategory, warningCode, helpKeyword, file, lineNumber, columnNumber, endLineNumber, endColumnNumber, message, messageArgs);
        }

        public void LogWarningFromException(Exception exception)
        {
            if (this._log != null)
                this._log.LogWarningFromException(exception);
        }

        public void LogWarningFromException(Exception exception, bool showStackTrace)
        {
            if (this._log != null)
                this._log.LogWarningFromException(exception, showStackTrace);
        }

        public void LogWarningFromResources(string messageResourceName, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogWarningFromResources(messageResourceName, messageArgs);
        }

        public void LogWarningFromResources(string subcategoryResourceName, string warningCode, string helpKeyword, string file, int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber, string messageResourceName, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogWarningFromResources(subcategoryResourceName, warningCode, helpKeyword, file, lineNumber, columnNumber, endLineNumber, endColumnNumber, messageResourceName, messageArgs);
        }

        public void LogWarningWithCodeFromResources(string messageResourceName, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogWarningWithCodeFromResources(messageResourceName, messageArgs);
        }

        public void LogWarningWithCodeFromResources(string subcategoryResourceName, string file, int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber, string messageResourceName, params object[] messageArgs)
        {
            if (this._log != null)
                this._log.LogWarningWithCodeFromResources(subcategoryResourceName, file, lineNumber, columnNumber, endLineNumber, endColumnNumber, messageResourceName, messageArgs);
        }

        #endregion


        public bool withPdb { get; set; }
    }


}
